/*  
 *  Copyright (c) 2008 Adrian Necimal <necimal@gmail.com>
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://app/jsmodules/sbProperties.jsm");

/*
 * The sbScrobbler component
 */
function SongbirdScrobbler() {
  this.wrappedJSObject = this;
};

SongbirdScrobbler.prototype = {
  classDescription: "Songbird Audioscrobbler Javascript XPCOM Component",
  classID: Components.ID("{0D038D3B-F087-4827-9A84-55DB7CE9F0DA}"),
  contractID: "@necimal.com/scrobbler;1",
  QueryInterface: XPCOMUtils.generateQI([]),
  AS_PLUGIN_ID: "sbd"
}

SongbirdScrobbler.prototype.onMediacoreEvent = function(ev) {
  switch (ev.type) {
    case Ci.sbIMediacoreEvent.TRACK_CHANGE:
      this.postCommand("START c=" + this.AS_PLUGIN_ID +
                       "&a=" + this.escapeString(ev.data.getProperty(SBProperties.artistName)) +
                       "&t=" + this.escapeString(ev.data.getProperty(SBProperties.trackName)) +
                       "&b=" + this.escapeString(ev.data.getProperty(SBProperties.albumName)) +
                       "&l=" + Math.round(ev.data.getProperty(SBProperties.duration)/1000000) +
                       "&p=" + this.escapeString(ev.data.getProperty(SBProperties.contentURL)) + "\n");
      break;
    case Ci.sbIMediacoreEvent.STREAM_START:
      this.postCommand("RESUME c=" + this.AS_PLUGIN_ID + "\n");
      break;
    case Ci.sbIMediacoreEvent.STREAM_PAUSE:
      this.postCommand("PAUSE c=" + this.AS_PLUGIN_ID + "\n");
      break;
    case Ci.sbIMediacoreEvent.STREAM_END:
    case Ci.sbIMediacoreEvent.STREAM_STOP:
      this.postCommand("STOP c=" + this.AS_PLUGIN_ID + "\n");
      break;
  }
}
  
SongbirdScrobbler.prototype.init = function() {
  // read some settings
  var prefsService = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
  
  this._autolaunch = prefsService.getBoolPref("extensions.songbird-scrobbler.autolaunch");
  this._customexe = prefsService.getCharPref("extensions.songbird-scrobbler.customexe");
  this._debug = prefsService.getBoolPref("extensions.songbird-scrobbler.debug");
  this._host = prefsService.getCharPref("extensions.songbird-scrobbler.host");
  this._port = prefsService.getIntPref("extensions.songbird-scrobbler.port");
  
  this._transportService = Cc["@mozilla.org/network/socket-transport-service;1"]
                             .getService(Ci.nsISocketTransportService);
  
  this._console = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
  
  this._cmdthread = Cc["@mozilla.org/thread-manager;1"].getService().newThread(0);
  this._nextCmdId = 0;
  
  this.logMessage("debugging on");
  
  if (this._autolaunch) {
    if (this.launchClient()) {
      this.logMessage("suessfully launched the last.fm client");
    }
    else {
      this.logMessage("couldn't launch the last.fm client!");
    }
  }
  else {
    this.logMessage("launching the last.fm client is disabled");
  }
  
  this.logMessage("host " + this._host + ":" + this._port);
}

SongbirdScrobbler.prototype.postCommand = function(cmd) {
  // get an id
  id = this._nextCmdId;
  this._nextCmdId++;
  
  // process the command asyncronously
  var cmdInfo = new this.CommandInfo(cmd, id, this);
  this._cmdthread.dispatch(cmdInfo, 0);
}

SongbirdScrobbler.prototype.logMessage = function(msg) {
  if (this._debug) {
    this._console.logStringMessage("songbird-scrobbler: " + msg);
  }
}

SongbirdScrobbler.prototype.escapeString = function(cmd) {
  if (!cmd) { return ""; }
  else { return cmd.replace(/&/ig, "&&"); }
}
  
SongbirdScrobbler.prototype.CommandInfo = function(cmd, id, scrobbler) {
  this._cmd = cmd;
  this._id = id;
  this._scrobbler = scrobbler;
  this.QueryInterface = XPCOMUtils.generateQI([Ci.nsIRunnable]);
  
  this.run = function() {
    this._scrobbler.logMessage("send (" + this._id + "): " + this._cmd);
    
    // get the transport
    var transport = this._scrobbler._transportService.createTransport(
                        null, 0, this._scrobbler._host, this._scrobbler._port, null);
    
    // open streams
    var instream = transport.openInputStream(transport.OPEN_BLOCKING, 0, 0);
    var outstream = transport.openOutputStream(transport.OPEN_BLOCKING, 0, 0);
    
    // convert to UTF8
    var utf8outstream = Cc["@mozilla.org/intl/converter-output-stream;1"]
                          .createInstance(Ci.nsIConverterOutputStream);
    utf8outstream.init(outstream, null, 0, '?');
    
    // write to the stream
    utf8outstream.writeString(this._cmd);
    utf8outstream.close();
    
    // read from the stream
    var sinstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
    sinstream.init(instream);

    try {
      var rcvd = "";
      do {
        rcvd += sinstream.read(50);
      } while (rcvd.length < 1);
      
      this._scrobbler.logMessage("rcvd (" + this._id + "): " + rcvd);
    }
    catch (ex) {
      this._scrobbler.logMessage("error (" + this._id + "): " + ex.name);
    }
    finally {
      sinstream.close();
    }
  }
}

SongbirdScrobbler.prototype.launchClient = function() {
  // try user-specified exe path
  if (this.startExe(this._customexe)) {
    return true;
  }
  
  // try getting path from windows registry
  var key = "Software\\Last.fm\\Client";
  
  try {
    var reg = Cc["@mozilla.org/windows-registry-key;1"].createInstance(Ci.nsIWindowsRegKey);
    reg.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, key, Ci.nsIWindowsRegKey.ACCESS_READ);
    command = reg.readStringValue("Path");
    if (this.startExe(command)) {
      return true;
    }
    reg.close();
  } catch (ex) { this.logMessage("ignoring error " + ex); }
  
  try {
    var reg = Cc["@mozilla.org/windows-registry-key;1"].createInstance(Ci.nsIWindowsRegKey);
    reg.open(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, key, Ci.nsIWindowsRegKey.ACCESS_READ);
    command = reg.readStringValue("Path");
    if (this.startExe(command)) {
      return true;
    }
    reg.close();
  } catch (ex) { this.logMessage("ignoring error " + ex); }
  
  // try some hard-coded defaults
  var defaults = ["C:\\Program Files\\Last.fm\\LastFM.exe",
                  "/Applications/Last.fm.app/Contents/MacOS/Last.fm",
                  "/usr/bin/lastfm",
                  "/usr/bin/last.fm",
                  "/usr/local/bin/lastfm",
                  "/usr/local/bin/last.fm"];
  for (var d in defaults) {
    if (this.startExe(defaults[d])) {
      return true;
    }
  }
  
  // give up
  return false;
}

SongbirdScrobbler.prototype.startExe = function(path) {
  // try to start the Last.fm client from the specified path
  var args = ["--tray"];
  
  try {
    this.logMessage("attempting to start " + path);
    var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
    file.initWithPath(path);
    
    if (file.exists && file.isExecutable) {
      var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
      process.init(file);
      process.run(false, args, args.length);
      return true;
    } else {
      return false;
    }
  } catch (ex) {
    return false;
  }
}

function NSGetModule(compMgr, fileSpec) {
  return XPCOMUtils.generateModule([SongbirdScrobbler]);
}
